﻿using System.IO.Ports;
using System.Text;
using Devonline.Communication.Abstractions;
using Microsoft.Extensions.Logging;

namespace Devonline.Communication.SerialPorts;

/// <summary>
/// 串口通信
/// </summary>
/// <typeparam name="T">串口对外交互的数据类型</typeparam>
public abstract class SerialPortCommunicator<T> : Communicator<T>, ICommunicator<T>
{
    protected readonly SerialPortOptions _setting;
    protected SerialPort? _serialPort;

    protected SerialPortCommunicator(ILogger<Communicator<T>> logger, SerialPortOptions setting) : base(logger, setting)
    {
        _setting = setting;
    }

    /// <summary>
    /// 当前连接状态
    /// </summary>
    public override bool IsConnected => _serialPort?.IsOpen ?? false;
    /// <summary>
    /// 启动通讯器
    /// </summary>
    public override async Task StartAsync()
    {
        _logger.LogWarning(LOG_COMMUNICATOR + "will open {com}", _options.Communicator, _setting.PortName);
        await base.StartAsync();
    }
    /// <summary>
    /// 关闭通讯器
    /// </summary>
    public override async Task StopAsync()
    {
        if (_serialPort == null)
        {
            return;
        }

        try
        {
            _serialPort.ErrorReceived -= OnErrorReceive;
            _serialPort.DataReceived -= OnReceive;
            _serialPort.PinChanged -= OnPinChange;

            if (_serialPort.IsOpen)
            {
                _serialPort.Close();
            }

            _serialPort.Dispose();
            await base.StopAsync();
        }
        catch (Exception ex)
        {
            OnError(ex);
        }
    }

    /// <summary>
    /// 注册事件
    /// </summary>
    protected override void OnInitial()
    {
        base.OnInitial();

        var portNames = SerialPort.GetPortNames();
        if (string.IsNullOrWhiteSpace(_setting.PortName) || (!portNames?.Any(x => x == _setting.PortName) ?? false))
        {
            _setting.PortName = portNames?.FirstOrDefault() ?? string.Empty;
        }

        if (string.IsNullOrWhiteSpace(_setting.PortName))
        {
            throw new Exception("缺少必须的串口名设置, 也没有读取到使用中的串口!");
        }

        _serialPort = new SerialPort(_setting.PortName, _setting.BaudRate, _setting.Parity, _setting.DataBits, _setting.StopBits);
        if (!string.IsNullOrWhiteSpace(_setting.Encoding))
        {
            _serialPort.Encoding = Encoding.GetEncoding(_setting.Encoding);
        }

        _serialPort.PinChanged += OnPinChange;
        _serialPort.DataReceived += OnReceive;
        _serialPort.ErrorReceived += OnErrorReceive;
    }
    /// <summary>
    /// 更换针脚事件处理方法
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    protected virtual async void OnPinChange(object? sender, SerialPinChangedEventArgs e)
    {
        try
        {
            ArgumentNullException.ThrowIfNull(_serialPort);
            OnError(new Exception($"串口 {_setting.PortName} 更换针脚, 事件类型: {e.EventType}, 将尝试重新打开当前串口!"));
            _serialPort.Close();
            await OnStartAsync();
        }
        catch (Exception ex)
        {
            OnError(ex);
        }
    }
    /// <summary>
    /// 收到错误事件处理方法
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    protected virtual async void OnErrorReceive(object? sender, SerialErrorReceivedEventArgs e)
    {
        try
        {
            ArgumentNullException.ThrowIfNull(_serialPort);
            OnError(new Exception($"串口 {_setting.PortName} 收到错误, 错误类型: {e.EventType}, 如果连接已中断, 将尝试重新打开当前串口!"));
            _serialPort.Close();
            await OnStartAsync();
        }
        catch (Exception ex)
        {
            OnError(ex);
        }
    }
    /// <summary>
    /// 串口接收数据的
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    protected abstract void OnReceive(object sender, SerialDataReceivedEventArgs e);
    /// <summary>
    /// 打开串口
    /// </summary>
    protected override Task OpenAsync()
    {
        try
        {
            ArgumentNullException.ThrowIfNull(_serialPort);
            if (!_serialPort.IsOpen)
            {
                _logger.LogWarning(LOG_COMMUNICATOR + " 正在打开串口 {com}", _options.Communicator, _setting.PortName);
                _serialPort.Open();
            }
        }
        catch (Exception ex)
        {
            OnError(ex);
        }

        return Task.CompletedTask;
    }
}

/// <summary>
/// 以字符串作为交互数据的串口通讯器
/// </summary>
public class SerialPortCommunicator : SerialPortCommunicator<string>, ICommunicator
{
    public SerialPortCommunicator(ILogger<SerialPortCommunicator<string>> logger, SerialPortOptions setting) : base(logger, setting) { }

    /// <summary>
    /// 将字符串写入串口
    /// </summary>
    /// <param name="value"></param>
    public override Task SendAsync(string value) => Task.Run(() =>
    {
        ArgumentNullException.ThrowIfNull(_serialPort);
        if (_serialPort.IsOpen && (!string.IsNullOrWhiteSpace(value)))
        {
            try
            {
                _logger.LogDebug(LOG_COMMUNICATOR + $"数据 {value} " + "将写入到串口 {com} 中", _options.Communicator, _setting.PortName);
                _serialPort.Write(value);
                _logger.LogInformation(LOG_COMMUNICATOR + $"数据 {value} " + "已写入到串口 {com} 中", _options.Communicator, _setting.PortName);
                OnSend(value);
            }
            catch (Exception ex)
            {
                OnError(ex);
            }
        }
    });

    /// <summary>
    /// 串口收到数据后触发的事件委托方法
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    protected override void OnReceive(object sender, SerialDataReceivedEventArgs e)
    {
        ArgumentNullException.ThrowIfNull(_serialPort);
        if (_serialPort.IsOpen)
        {
            try
            {
                var value = _serialPort.ReadExisting();
                _logger.LogInformation(LOG_COMMUNICATOR + "收到串口 {com} " + $"数据 {value}" + ", 事件类型 {eventType}", _options.Communicator, _setting.PortName, e.EventType);
                OnReceive(value);
            }
            catch (Exception ex)
            {
                OnError(ex);
            }
        }
    }
}